//  IDVI 1.0 source copyright 1996 Garth A. Dickie
//
//  This source is free for non-commercial use.  No warranty, etc.
//  Please acknowledge reuse by including the line:
//
//  "Based in part on IDVI 1.0 source copyright 1996 Garth A. Dickie"
//
//  in your documentation and source code.  For commercial use or
//  distribution, please contact the author.  Please also send
//  questions, comments, bug reports, or fixes.
//
//  Best Regards,
//  Garth A. Dickie
//  dickie@elastic.avid.com

package ibook.v10.idvi.display;

import ibook.v10.idvi.dvi.DVIFormatException;
import ibook.v10.idvi.dvi.DVITokenizer;
import ibook.v10.idvi.dvi.DVIDocument;

public class ContainerBlock extends Block {
    private final static int    kChildLengthInitial = 100;
    private final static int    kViewLengthInitial = 2;
    private final static int    kViewLengthIncrement = 2;

    private int                 childCount = 0;
    private Block[ ]            child;
    private int[ ]              x;
    private int[ ]              y;

    private int                 viewCount = 0;
    private ContainerView[ ]    view;

    //  addChild adds a child to the ContainerBlock, without notifying any
    //  attached ContainerView objects about the new child.  If the child
    //  is going to take time to load (for example, if it is another
    //  ContainerBlock), the parser should call flushChildren( ) after
    //  calling addChild, and before loading the contents of the child.

    void addChild( Block child, int x, int y ) {
        if( this.child == null ) {
            this.child = new Block[ kChildLengthInitial ];
            this.x     = new int  [ kChildLengthInitial ];
            this.y     = new int  [ kChildLengthInitial ];
        } else if( childCount == this.child.length )
            setChildLength( this.child.length * 2 );
        
        this.child[ childCount ] = child;
        this.x[ childCount ] = x;
        this.y[ childCount ] = y;

        childCount ++;
    }

    //  flushChildren notifies all attached ContainerView objects about
    //  the presence of new children.  This will cause each ContainerView
    //  to construct View objects for the new children and link them into
    //  the View heierarchy, allowing them to be displayed.

    void flushChildren( ) {
        for( int i = 0; i < viewCount; ++ i )
            view[ i ].addChildren( childCount, child, x, y );
    }

    //  doneAddingChildren really exists just to allow for the tightening
    //  up of oversized buffers.  There is a similar "childDoneLoading"
    //  method in the ActionBlock class, which is used to tell attached
    //  Views to add an underline, and in the DefinitionBlock class, which
    //  the attached Views use as a cue to tell their parents about their
    //  real expansion value.

    void doneAddingChildren( ) {
        if( child != null && childCount != child.length )
            setChildLength( childCount );
        
        for( int i = 0; i < viewCount; ++ i )
            view[ i ].doneAddingChildren( );
    }

    //  getView is called either directly from the ViewPanel constructor
    //  or indirectly through another getView call.  In the ContainerBlock
    //  we maintain a list of all attached ContainerViews, for use by the
    //  flushChildren( ) and doneAddingChildren( ) methods.

    View getView( ScaledColorScheme colorScheme, int scale, ViewPanel panel ) {
        ContainerView result = new ContainerView( colorScheme, scale, panel );
        result.addChildren( childCount, child, x, y );

        addView( result );

        return result;
    }



    //  addView is synchronized where nothing else is because of
    //  a slight tweak to the simple synchronization scheme being
    //  used.  Instead of obtaining a write lock on the whole
    //  structure during getView( ), we obtain a read lock.
    //  The only modification we make to the existing Block/View
    //  structure is to add a View to the list maintained by some
    //  of the Blocks; this list is only used at times when a
    //  write lock has been obtained, so we're ok.  We just have
    //  to synchronize to avoid multiple getView( ) calls competing.

    private synchronized void addView( ContainerView newView ) {
        if( view == null )
            view = new ContainerView[ kViewLengthInitial ];
        else if( viewCount == view.length )
            setViewLength( view.length + kViewLengthIncrement );

        view[ viewCount ] = newView;
        viewCount ++;
    }

    private void setChildLength( int length ) {
        Block[ ] oldChild = child;
        int[ ] oldX = x;
        int[ ] oldY = y;

        child = new Block[ length ];
        x = new int[ length ];
        y = new int[ length ];

        int copyLength = Math.min( length, oldChild.length );

        System.arraycopy( oldChild, 0, child, 0, copyLength );
        System.arraycopy( oldX, 0, x, 0, copyLength );
        System.arraycopy( oldY, 0, y, 0, copyLength );
    }

    private void setViewLength( int length ) {
        ContainerView[ ] oldView = view;
        view = new ContainerView[ length ];
        System.arraycopy( oldView, 0, view, 0, Math.min( length, oldView.length ));
    }
}
